/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.hiruss.imageprocessor.util;

import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.filechooser.FileNameExtensionFilter;

/**
 *
 * @author Hiru
 */
public class ImageHandler {

    public static BufferedImage loadImage(Component parent) {
        File defaultDir = new File(".");
        JFileChooser fileChooser = new JFileChooser();
        fileChooser.setMultiSelectionEnabled(false);
        fileChooser.setCurrentDirectory(defaultDir);
        FileNameExtensionFilter filter = new FileNameExtensionFilter("Image Files (.jpg .png .bmp .gif)", "jpg", "png", "bmp", "gif");
        fileChooser.setFileFilter(filter);
        fileChooser.showOpenDialog(parent);
        File file = fileChooser.getSelectedFile();
        try {
            return (file == null ? null : ImageIO.read(file));
        } catch (IOException ex) {
            Logger.getLogger(ImageHandler.class.getName()).log(Level.SEVERE, null, ex);
            return null;
        }
    }

    public static void saveImage(BufferedImage image, Component parent) {
        JFileChooser fileChooser = new JFileChooser();
        FileNameExtensionFilter filter1 = new FileNameExtensionFilter(
                "JPEG (jpg)", "jpg");
        FileNameExtensionFilter filter2 = new FileNameExtensionFilter(
                "PNG (png)", "png");
        FileNameExtensionFilter filter3 = new FileNameExtensionFilter(
                "GIF (gif)", "gif");

        fileChooser.addChoosableFileFilter(filter1);
        fileChooser.addChoosableFileFilter(filter2);
        fileChooser.addChoosableFileFilter(filter3);
        fileChooser.setCurrentDirectory(new File("."));
        fileChooser.setAcceptAllFileFilterUsed(false);
        fileChooser.setFileFilter(filter1);

        if (fileChooser.showSaveDialog(parent) == JFileChooser.APPROVE_OPTION) {
            String path = fileChooser.getSelectedFile().getAbsolutePath();
            String type = ((FileNameExtensionFilter) (fileChooser.getFileFilter())).getExtensions()[0];
            if (type == null) {
                type = "jpg";
            }
            path = path + "." + type;
            try {
                File outputfile = new File(path);
                ImageIO.write(image, type, outputfile);
                JOptionPane.showMessageDialog(parent, "File " + path + " saved successfully", "File Save", JOptionPane.INFORMATION_MESSAGE);
            } catch (IOException ex) {
                JOptionPane.showMessageDialog(parent, "Unable to save file!\n"
                        + ex.getMessage(), "File Save", JOptionPane.ERROR_MESSAGE);
            }
        }
    }

    public static BufferedImage duplicateImage(BufferedImage image) {
        BufferedImage copy = new BufferedImage(image.getWidth(), image.getHeight(), image.getType());
        image.copyData(copy.getWritableTile(0, 0));
        copy.releaseWritableTile(0, 0);
        return copy;
    }

    public static BufferedImage scale(BufferedImage source, int width, int height, Object interpolationTypeRenderingHint) {
        BufferedImage dest = new BufferedImage(width, height, source.getType());
        Graphics2D g = dest.createGraphics();
        g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, interpolationTypeRenderingHint);
        g.drawImage(source, 0, 0, width, height, null);
        g.dispose();
        return dest;
    }

    public static BufferedImage applyFilter(BufferedImage image, int[][] mask, Filters.TYPE type, int threasholdOrP) {
        int[][][] matrixData = ImageHandler.convertToMatrix(image);
        int[][] maskedRed = ImageHandler.applyMask(matrixData[0], mask, type, threasholdOrP);
        int[][] maskedGreen = ImageHandler.applyMask(matrixData[1], mask, type, threasholdOrP);
        int[][] maskedBlue = ImageHandler.applyMask(matrixData[2], mask, type, threasholdOrP);
        int width = image.getWidth();
        int height = image.getHeight();
        BufferedImage dest = new BufferedImage(width, height, image.getType());
        for (int i = 0; i < height; i++) {
            for (int j = 0; j < width; j++) {
                Color c = new Color(image.getRGB(j, i));
                if (!Filters.TYPE.AVARAGE_THREASHOLD.equals(type)
                        || (Math.abs(c.getRed() - maskedRed[i][j]) < threasholdOrP
                        && Math.abs(c.getGreen() - maskedGreen[i][j]) < threasholdOrP
                        && Math.abs(c.getBlue() - maskedBlue[i][j]) < threasholdOrP)) {
                    c = new Color(
                            maskedRed[i][j],
                            maskedGreen[i][j],
                            maskedBlue[i][j],
                            c.getAlpha());
                }
                dest.setRGB(j, i, c.getRGB());
            }
        }
        return dest;
    }

    public static int[][][] convertToMatrix(BufferedImage image) {
        int height = image.getHeight();
        int width = image.getWidth();
        int[][][] result = new int[3][height][width];
        for (int i = 0; i < height; i++) {
            for (int j = 0; j < width; j++) {
                Color c = new Color(image.getRGB(j, i));
                result[0][i][j] = c.getRed();
                result[1][i][j] = c.getGreen();
                result[2][i][j] = c.getBlue();
            }
        }
        return result;
    }

    private static int[][] applyMask(int[][] image, int[][] mask, Filters.TYPE type, int threasholdOrP) {
        int height = image.length;
        int width = image[0].length;
        int mid = (int) Math.floor(mask.length / 2);
        int[][] result = new int[height][width];

        int total = 0;
        for (int i = 0; i < mask.length; i++) {
            for (int j = 0; j < mask[0].length; j++) {
                total += mask[i][j];
            }
        }

        if (threasholdOrP > ((total - 1) / 2)) {
            threasholdOrP = (total - 1) / 2;
        }

        int maskWidth = mask.length;
        int[] sortedValues = new int[maskWidth * maskWidth];
        for (int i = mid; i < height - mid; i++) {
            for (int j = mid; j < width - mid; j++) {
                int value = 0;
                for (int mi = -mid; mi <= mid; mi++) {
                    for (int mj = -mid; mj <= mid; mj++) {
                        int v = image[i + mi][j + mj] * mask[mid + mi][mid + mj];
                        sortedValues[maskWidth * (mi + mid) + (mj + mid)] = v;
                        value += v;
                    }
                }
                Arrays.sort(sortedValues);
                int totalValue = 0;
                if (Filters.TYPE.ALPHA_TRIMMED.equals(type)) {
                    for (int x = threasholdOrP; x < total - threasholdOrP; x++) {
                        totalValue += sortedValues[x];
                    }
                }
                switch (type) {
                    case AVARAGE:
                    case AVARAGE_THREASHOLD:
                        result[i][j] = value / total;
                        break;
                    case MAX:
                        result[i][j] = sortedValues[sortedValues.length - 1];
                        break;
                    case MIN:
                        result[i][j] = sortedValues[0];
                        break;
                    case MEDIAN:
                        result[i][j] = sortedValues[sortedValues.length / 2];
                        break;
                    case MID:
                        result[i][j] = (sortedValues[sortedValues.length - 1] + sortedValues[0]) / 2;
                        break;
                    case ALPHA_TRIMMED:
                        result[i][j] = totalValue / (total - (2 * threasholdOrP));
                        break;
                    case EDGE:
                        result[i][j] = Math.abs(value);
                        break;
                }
            }
        }
        return result;
    }

    public static BufferedImage applyEdgeDetection(BufferedImage image, Filters.TYPE type) {

        image = PointOperations.averageGrayscale(image);
        int width = image.getWidth();
        int height = image.getHeight();
        int[][][] matrixData = ImageHandler.convertToMatrix(image);
        int[][] modifiedIntensties, modifiedIntensties2 = null;
        int[][] mask = null;
        switch (type) {
            case SOBEL_X:
            case SOBEL_BOTH:
                mask = Filters.EDGE_FILTER_SOBEL_X;
                break;
            case SOBEL_Y:
                mask = Filters.EDGE_FILTER_SOBEL_Y;
                break;
            case LAPLACIAN:
                mask = Filters.EDGE_FILTER_LAPLACIAN;
                break;
        }
        modifiedIntensties = applyMask(matrixData[0], mask, Filters.TYPE.EDGE, -1);
        if (Filters.TYPE.SOBEL_BOTH.equals(type)) {
            modifiedIntensties2 = applyMask(matrixData[0], Filters.EDGE_FILTER_SOBEL_Y, Filters.TYPE.EDGE, -1);
        }

        BufferedImage dest = new BufferedImage(width, height, image.getType());
        for (int i = 0; i < height; i++) {
            for (int j = 0; j < width; j++) {
                int newIntensity;
                if (Filters.TYPE.SOBEL_BOTH.equals(type)) {
                    newIntensity = (int) Math.sqrt((modifiedIntensties[i][j] * modifiedIntensties[i][j]
                            + modifiedIntensties2[i][j] * modifiedIntensties2[i][j]));
                } else {
                    newIntensity = modifiedIntensties[i][j];
                }
                Color c = new Color(image.getRGB(j, i));
                dest.setRGB(j, i, colorToRGB(c.getAlpha(), newIntensity, newIntensity, newIntensity));
            }
        }
        return dest;
    }

    public static BufferedImage applyEdgeDetectionKirsch(BufferedImage image) {
        image = PointOperations.averageGrayscale(image);
        int width = image.getWidth();
        int height = image.getHeight();
        int[][][] matrixData = ImageHandler.convertToMatrix(image);
        int[][][] modifiedIntensties = new int[Filters.EDGE_FILTER_HRISH.length][height][width];
        for (int x = 0; x < Filters.EDGE_FILTER_HRISH.length; x++) {
            modifiedIntensties[x] = applyMask(matrixData[0], Filters.EDGE_FILTER_HRISH[x], Filters.TYPE.EDGE, -1);
        }

        BufferedImage dest = new BufferedImage(width, height, image.getType());
        for (int i = 0; i < height; i++) {
            for (int j = 0; j < width; j++) {
                int max = modifiedIntensties[0][i][j];
                for (int x = 1; x < modifiedIntensties.length; x++) {
                    if (modifiedIntensties[x][i][j] > max) {
                        max = modifiedIntensties[x][i][j];
                    }
                }
                Color c = new Color(image.getRGB(j, i));
                dest.setRGB(j, i, colorToRGB(c.getAlpha(), max, max, max));
            }
        }
        return dest;
    }

    public static BufferedImage applyThreshold(BufferedImage image, int threshold) {
        int w = image.getWidth();
        int h = image.getHeight();
        BufferedImage dest = new BufferedImage(w, h, image.getType());
        for (int i = 0; i < w; i++) {
            for (int j = 0; j < h; j++) {
                Color c = new Color(image.getRGB(i, j));

                int r = c.getRed() > threshold ? 255 : 0;
                int g = c.getGreen() > threshold ? 255 : 0;
                int b = c.getBlue() > threshold ? 255 : 0;

                c = new Color(r, g, b, c.getAlpha());
                dest.setRGB(i, j, c.getRGB());
            }
        }
        return dest;
    }

    public static int colorToRGB(int alpha, int red, int green, int blue) {
        int newPixel = 0;
        newPixel += alpha;
        newPixel = newPixel << 8;
        newPixel += red;
        newPixel = newPixel << 8;
        newPixel += green;
        newPixel = newPixel << 8;
        newPixel += blue;
        return newPixel;
    }
}
